/*
 * PerlinSampler.cpp
 *
 *  Created on: Oct 5, 2012
 *      Author: burek
 */

#include "PerlinSampler.h"
#include <cstdlib>
#include <cstdio>
#include <cmath>

#include "math/util.h"

//float persistence;
//int maxLevels;
//Uint32 startSeed;

Uint32 pseudoRandom(Uint32 seed)
{
	//seed = (seed * 58321) + 11113;
	//return (seed >> 16);
	seed += 1726345;
	seed *= 123456791;
	seed = (seed << 16) | (seed >> 16);
	seed *= 234567891;
	return (seed >>16);
}

PerlinSampler::PerlinSampler(float Persistence, int MaxLevels)
{
	persistence = Persistence;
	maxLevels = MaxLevels;
	startSeed = rand();

	startShift = maxLevels - 1;
	startDiv = pow(2.0f, maxLevels - 1);
	iStartDiv = (Uint32)startDiv;

	wrapAround = 16777216;

	for(int i = 0; i < 256; ++i)
	{
		vectorField[i].set(randRangeF(-1.0f, 1.0f), randRangeF(-1.0f, 1.0f));
		vectorField[i].normalizeIP();
	}
}

PerlinSampler::~PerlinSampler() {}

//float samp()

float PerlinSampler::internalSample(Uint32 x, Uint32 y) const
{
	x = x % wrapAround;
	y = y % wrapAround;
	Uint32 uid = pseudoRandom(x * 65207) * pseudoRandom(y * 371);
	int32 psr = (int32)pseudoRandom(uid);
	return (((psr % 1000) - 500) / 500.0f);
}

inline Vec2D PerlinSampler::getVector(Uint32 x, Uint32 y) const
{
	x = x % wrapAround;
	y = y % wrapAround;
	Uint32 uid = pseudoRandom(x * 65207) * pseudoRandom(y * 371);
	return vectorField[uid & 0xFF];
}

inline float PerlinSampler::getValue(Uint32 x, Uint32 y, float dx, float dy) const
{
	Vec2D edges[4] = { getVector(x, y), getVector(x + 1, y), getVector(x, y + 1), getVector(x + 1, y + 1) };
	Vec2D targets[4] = { Vec2D(dx, dy), Vec2D(-1.0f + dx, dy), Vec2D(dx, -1.0f + dy), Vec2D(-1.0f + dx, -1.0f + dy)};
	float dots[4];
	for(int i = 0; i < 4; ++i) dots[i] = edges[i].dot(targets[i]);

	float edx = ease(dx);
	float edy = ease(dy);
	float top = dots[0] + (dots[1] - dots[0]) * edx;
	float bottom = dots[2] + (dots[3] - dots[2]) * edx;
	return top + (bottom - top) * edy;

}

float PerlinSampler::multiSample(float x, float y) const
{
	float total = 0.0f;
	float pers = 1.0f;
	float div = startDiv;

	for(Uint32 l = 0; l < maxLevels; ++l)
	{
		float nx = x / div;
		float ny = y / div;
		Uint32 ix = (Uint32)nx;
		Uint32 iy = (Uint32)ny;
		float dx = nx - floor(nx); // nx - ix;
		float dy = ny - floor(ny); //ny - iy;

		total += getValue(ix, iy, dx, dy) * pers;

		div /= 2.0f;
		pers *= persistence;
	}

	return total;
}


float PerlinSampler::sample(Uint32 x, Uint32 y, float dx, float dy) const
{
	float total = 0.0f;
	float pers = 1.0f;
	Uint32 shift = startShift;
	float div = startDiv;
	Uint32 idiv = iStartDiv;

	float v1, v2, v3, v4, u, v, cv;

	for(Uint32 l = 0; l < maxLevels - 1; ++l)
	{
		v1 = internalSample((x >> shift), (y >> shift));
		v2 = internalSample((x >> shift)+1, (y >> shift));
		v3 = internalSample((x >> shift), (y >> shift)+1);
		v4 = internalSample((x >> shift)+1, (y >> shift)+1);

		u = (x % idiv) / div - ((x % idiv) >> shift);
		v = (y % idiv) / div - ((y % idiv) >> shift);

		cv = LERP(LERP(v1, v2, u), LERP(v3, v4, u), v);

		total += cv * pers;
		pers *= persistence;
		shift--;
		div *= 0.5f;
		idiv >>= 1;
	}

	v1 = internalSample((x >> shift), (y >> shift));
	v2 = internalSample((x >> shift)+1, (y >> shift));
	v3 = internalSample((x >> shift), (y >> shift)+1);
	v4 = internalSample((x >> shift)+1, (y >> shift)+1);

	cv = LERP(LERP(v1, v2, dx), LERP(v3, v4, dx), dy);
	total += cv * pers;

	return total;
}
